package com.proxy.customer;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author: AaronChun
 * @create: 2019-03-25 11:24
 * @description:
 **/
public class CustomerProxy {

    private static final String LN = "\n";

    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    private CustomerProxy() {

    }


    private static final String PRE_FILENAME = "$proxy";


    public static Object newProxyInstance(CustomerClassLoader loader,
                                          Class<?>[] interfaces,
                                          CustomerInvocationHandler h)
            throws IllegalArgumentException, IOException {

        //1.生成proxy源文件

        String src = generateSource(interfaces);

//        byte[] bytes = ProxyGenerator.generateProxyClass("$proxy1", interfaces);

        //2.生成文件

        String filePath = interfaces[0].getResource("").getPath();

        File f = new File(filePath + PRE_FILENAME + nextUniqueNumber.get() + ".java");
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();
        //3.编译原文件
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable iterable = manager.getJavaFileObjects(f);
        JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(null, manager, null, null, null, iterable);
        compilationTask.call();
        manager.close();


        //4.动态添加到JVM中
        try {
            Class proxyClass = loader.findClass(interfaces[0].getPackage().getName() + "." + PRE_FILENAME + nextUniqueNumber.get());
            Constructor c = proxyClass.getConstructor(CustomerInvocationHandler.class);
            return c.newInstance(h);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }finally {
            //f.delete();
        }

        return null;

    }

    private static String generateSource(Class<?>[] interfaces) {

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < interfaces.length; i++) {
            Class clazz = interfaces[i];
            sb.append(clazz.getPackage()).append(";").append(LN);

            sb.append("import java.lang.reflect.Method;").append(LN);
            sb.append("import com.proxy.customer.CustomerInvocationHandler;").append(LN);
            sb.append("import java.lang.reflect.UndeclaredThrowableException;").append(LN);
            long num = nextUniqueNumber.incrementAndGet();
            String className = PRE_FILENAME + num;
            sb.append("public final class ").append(className).append(" implements ").append(clazz.getSimpleName()).append("{").append(LN);
            sb.append("private CustomerInvocationHandler h;").append(LN);

            sb.append("public ").append(className).append("(CustomerInvocationHandler h){").append(LN);
            sb.append("this.h = h;").append(LN);
            sb.append("}").append(LN);


            for (Method method : clazz.getMethods()) {
                sb.append("public final").append(" ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(");
                Class[] typeVariables = method.getParameterTypes();

                int count = 0;
                for (int j = 0; j < typeVariables.length; j++) {

                    sb.append(typeVariables[i].getName()).append(" ").append("var").append(++count);
                }

                sb.append("){").append(LN);
                sb.append("try{").append(LN);
                sb.append("Method m = ").append(clazz.getName()).append(".class.getMethod(\"").append(method.getName()).append("\"");

                if (count != 0) {
                    sb.append(",Class.forName(");

                    for (int j = 0; j < count; j++) {
                        sb.append("\"").append(typeVariables[i].getName()).append("\"").append(")");
                        if (j != count - 1) {
                            sb.append(",");
                        }
                    }
                    sb.append(");").append(LN);


                } else {
                    sb.append(");").append(LN);

                }


                if (!method.getReturnType().getName().equals("void")) {

                    sb.append("return (").append("Integer").append(")");
                }
                sb.append("this.h.invoke(this,m,");
                if (count == 0) {
                    sb.append("(Object[])null);");
                } else {
                    sb.append("new Object[]{");

                    for (int j = 0; j < count; j++) {
                        sb.append("var").append(count);
                        if (j != count - 1) {
                            sb.append(",");
                        }
                    }
                    sb.append("});").append(LN);


                }
                sb.append("}catch(Throwable e){").append(LN);
                sb.append("e.printStackTrace();throw new UndeclaredThrowableException(e);}").append(LN);

                sb.append("}").append(LN);
            }

        }


        sb.append("}").append(LN);


        return sb.toString();


    }


    public static void main(String[] args) {

//        Man man = new Man();

//        System.out.println(CustomerProxy.generateSource(man.getClass().getInterfaces()));

        /*String filePath = CustomerProxy.class.getResource("").getPath();
        System.out.println(filePath);*/
    }

}
